﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.Net;
using System.Runtime.InteropServices;
using System.IO;


namespace Engine.NetClasses
{
    // Implements the connection logic for the socket server.  
    // After accepting a connection, all data read from the client 
    // is sent back to the client. The read and echo back to the client pattern 
    // is continued until the client disconnects.
    public class Server
    {
        const int protocol = 713;
        const string logDir = "serverLog/";
        private int m_numConnections;   // the maximum number of connections the sample is designed to handle simultaneously 
        private int m_ssesionCounter;
        private int m_receiveBufferSize;// buffer size to use for each socket I/O operation 
        BufferManager m_bufferManager;  // represents a large reusable set of buffers for all socket operations
        const int opsToPreAlloc = 2;    // read, write (don't alloc buffer space for accepts)
        Socket listenSocket;            // the socket used to listen for incoming connection requests
        // pool of reusable SocketAsyncEventArgs objects for write, read and accept socket operations
        SocketAsyncEventArgsPool m_readWritePool;
        int m_totalBytesRead;           // counter of the total # bytes received by the server
        int m_numConnectedSockets;      // the total number of clients connected to the server 
        Semaphore m_maxNumberAcceptedClients;
        FileStream logFile;
        StreamWriter sw;
       public  bool serverRun = false;
        ServerOptions s;
        private ServerState m_serverState;
        private ServerLogs m_serverLog;
        private ServerInput m_input;
        // Create an uninitialized server instance.  
        // To start the server listening for connection requests
        // call the Init method followed by Start method 
        //
        // <param name="numConnections">the maximum number of connections the sample is designed to handle simultaneously</param>
        // <param name="receiveBufferSize">buffer size to use for each socket I/O operation</param>
        public Server(int numConnections, int receiveBufferSize)
        {
            s = ServerOptions.Load();
            m_serverState = new ServerState(numConnections);
            m_serverLog = new ServerLogs(s);
            m_input = new ServerInput(m_serverLog, s, m_serverState,this);
            NetPacket.NetPacketBild(true);
            m_totalBytesRead = 0;
            m_numConnectedSockets = 0;
            m_ssesionCounter = 0;
            m_numConnections = numConnections;
            m_receiveBufferSize = receiveBufferSize;
            // allocate buffers such that the maximum number of sockets can have one outstanding read and 
            //write posted to the socket simultaneously  
            m_bufferManager = new BufferManager(receiveBufferSize * numConnections * opsToPreAlloc,
                receiveBufferSize);

            m_readWritePool = new SocketAsyncEventArgsPool(numConnections);
            m_maxNumberAcceptedClients = new Semaphore(numConnections, numConnections);
        }

        // Initializes the server by preallocating reusable buffers and 
        // context objects.  These objects do not need to be preallocated 
        // or reused, but it is done this way to illustrate how the API can 
        // easily be used to create reusable objects to increase server performance.
        //
        public void Init()
        {
            // Allocates one large byte buffer which all I/O operations use a piece of.  This gaurds 
            // against memory fragmentation
            m_bufferManager.InitBuffer();

            // preallocate pool of SocketAsyncEventArgs objects
            SocketAsyncEventArgs readWriteEventArg;

            for (int i = 0; i < m_numConnections; i++)
            {
                //Pre-allocate a set of reusable SocketAsyncEventArgs
                readWriteEventArg = new SocketAsyncEventArgs();
                readWriteEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed);
                readWriteEventArg.UserToken = new AsyncUserToken();

                // assign a byte buffer from the buffer pool to the SocketAsyncEventArg object
                m_bufferManager.SetBuffer(readWriteEventArg);

                // add SocketAsyncEventArg to the pool
                m_readWritePool.Push(readWriteEventArg);
            }

        }

        // Starts the server such that it is listening for 
        // incoming connection requests.    
        //
        // <param name="localEndPoint">The endpoint which the server will listening 
        // for connection requests on</param>
        public void Start(IPEndPoint localEndPoint)
        { 
            DirectoryInfo dir = new DirectoryInfo(logDir);
            if (!dir.Exists)
            {
                dir.Create();
            }
            using (logFile = new FileStream(logDir + DateTime.Now.ToString("yyyyMMddHHmmss") + ".log", FileMode.OpenOrCreate, FileAccess.ReadWrite))
            {

                using ( sw = new StreamWriter(logFile))
                {
            // create the socket which listens for incoming connections
            listenSocket = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            listenSocket.Bind(localEndPoint);
            // start the server with a listen backlog of 100 connections
            listenSocket.Listen(100);

            // post accepts on the listening socket
            StartAccept(null);
            sw.WriteLine("Server logfile: {0}", logDir + DateTime.Now.ToString("yyyyMMddHHmmss") + ".log");
            sw.WriteLine("Server start time: {0}", DateTime.Now.ToString());
            sw.WriteLine("Server ip and port: {0}", localEndPoint.ToString());
           // sw.WriteLine("Server port: {0}", localEndPoint.Port.ToString());
            sw.WriteLine("Server max connection: {0}", m_numConnections);
           
            
            serverRun = true;
            m_serverState.Online = serverRun;
                    //Console.WriteLine("{0} connected sockets with one outstanding receive posted to each....press any key", m_outstandingReadCount);
                    Console.WriteLine("-help or -? get help");                   
                    while (serverRun)
                    {
                       
                        string input = Console.ReadLine();
                        
                        Inputconsole(input);
                       // m_input.doinput();
                        //Console.ReadKey(); 
                    }
             m_serverState.Online = serverRun;
                }
            }
            m_serverLog.Close();
        }

        private void Inputconsole(string line)
        {

            m_input.DoCommand(line.ToLower());
           

        }
        // Begins an operation to accept a connection request from the client 
        //
        // <param name="acceptEventArg">The context object to use when issuing 
        // the accept operation on the server's listening socket</param>
        public void StartAccept(SocketAsyncEventArgs acceptEventArg)
        {
            if (acceptEventArg == null)
            {
                acceptEventArg = new SocketAsyncEventArgs();
                acceptEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(AcceptEventArg_Completed);
            }
            else
            {
                // socket must be cleared since the context object is being reused
                acceptEventArg.AcceptSocket = null;
            }

            m_maxNumberAcceptedClients.WaitOne();
            bool willRaiseEvent = listenSocket.AcceptAsync(acceptEventArg);
            if (!willRaiseEvent)
            {
                ProcessAccept(acceptEventArg);
            }
        }

        // This method is the callback method associated with Socket.AcceptAsync 
        // operations and is invoked when an accept operation is complete
        //
        void AcceptEventArg_Completed(object sender, SocketAsyncEventArgs e)
        {
            ProcessAccept(e);
        }

        private void ProcessAccept(SocketAsyncEventArgs e)
        {
            Interlocked.Increment(ref m_numConnectedSockets);
            Interlocked.Increment(ref m_ssesionCounter);
            m_serverState.NewClient();
           
                
            sw.WriteLine("Client connection accepted. There are {0} clients connected to the server. Session id:{1}",
                m_numConnectedSockets, (uint)m_ssesionCounter);
            // Get the socket for the accepted client connection and put it into the 
            //ReadEventArg object user token
            SocketAsyncEventArgs readEventArgs = m_readWritePool.Pop();
            ((AsyncUserToken)readEventArgs.UserToken).Socket = e.AcceptSocket;
            ((AsyncUserToken)readEventArgs.UserToken).Id = (uint)m_ssesionCounter;
            byte[] t = InitClient((uint)m_ssesionCounter);
            PacketsBilder p = new PacketsBilder(t,0);
            //send Client Init pack
            ((AsyncUserToken)readEventArgs.UserToken).SetFromServer(p);
            //Array.Copy(t, 0, readEventArgs.Buffer,readEventArgs.Offset, t.Length);
            //e.SetBuffer(readEventArgs.Offset, t.Length);          
            //bool willRaiseEvent = ((AsyncUserToken)readEventArgs.UserToken).Socket.SendAsync(readEventArgs);
            //if (!willRaiseEvent)
            //{
            //    ProcessSend(readEventArgs);
            //}

            doAction(readEventArgs);
            sw.WriteLine("Cliend id {0} send Init packet", (uint)m_ssesionCounter);            
           
          
            // Accept the next connection request
            StartAccept(e);
        }

        // This method is called whenever a receive or send operation is completed on a socket 
        //
        // <param name="e">SocketAsyncEventArg associated with the completed receive operation</param>
        void IO_Completed(object sender, SocketAsyncEventArgs e)
        {
            // determine which type of operation just completed and call the associated handler
            switch (e.LastOperation)
            {
                case SocketAsyncOperation.Receive:
                    ProcessReceive(e);
                    break;
                case SocketAsyncOperation.Send:
                    ProcessSend(e);
                    break;
                    
                default:
                    throw new ArgumentException("The last operation completed on the socket was not a receive or send");
            }

        }

        // This method is invoked when an asynchronous receive operation completes. 
        // If the remote host closed the connection, then the socket is closed.  
        // If data was received then the data is echoed back to the client.
        //
        private void ProcessReceive(SocketAsyncEventArgs e)
        {
            // check if the remote host closed the connection
            AsyncUserToken token = (AsyncUserToken)e.UserToken;
            if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
            {
               SerializationPacket(e); 
               //Interlocked.Add(ref m_totalBytesRead, e.BytesTransferred);
             bool needrecive= doAction(e);
              
              
               
            }
            else
            {
                CloseClientSocket(e);
            }
        }

        private void SerializationPacket(SocketAsyncEventArgs e)
        {
            if (e.Buffer[e.Offset+2]!=0)
            {


                PacketsBilder pa = new PacketsBilder(e.Buffer, e.Offset);
            AsyncUserToken token = (AsyncUserToken)e.UserToken;
            token.SetFromClient(pa);
            }
            //RequestLoginAuth r = pa.getStryct();
            //StringBuilder login = new StringBuilder();
            //login.Append(r.login);
            //StringBuilder pass = new StringBuilder();
            //pass.Append(r.password);
            //sw.WriteLine("recive from client id {0} packet {1} login:{2} pass {3}", 
            //    (e.UserToken as AsyncUserToken).Id,
            //    (PacketTypeClient)pa.Header._ptype,               
            //    login.ToString(),
            //    pass.ToString());
            
        }
 
        
            
        // This method is invoked when an asynchronous send operation completes.  
        // The method issues another receive on the socket to read any additional 
        // data sent from the client
        //
        // <param name="e"></param>

        private void ProcessSend(SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {
                StartRecive(e);

            }
            else
            {
                CloseClientSocket(e);
            }
        }
        private void CloseClientSocket(SocketAsyncEventArgs e)
        {
            AsyncUserToken token = e.UserToken as AsyncUserToken;

            // close the socket associated with the client
            try
            {
                token.Socket.Shutdown(SocketShutdown.Send);
            }
            // throws if client process has already closed
            catch (Exception) { }
            token.Socket.Close();

            // decrement the counter keeping track of the total number of clients connected to the server
            Interlocked.Decrement(ref m_numConnectedSockets);
            m_serverState.ClientDisconect();
            m_maxNumberAcceptedClients.Release();
            sw.WriteLine("A client id {1} has been disconnected from the server. There are {0} clients connected to the server", m_numConnectedSockets,token.Id);

            // Free the SocketAsyncEventArg so they can be reused by another client
            m_readWritePool.Push(e);
        }
        private byte[] InitClient(uint id)
        {
            byte[] temp = new byte[Marshal.SizeOf(typeof(HeaderPacket)) + Marshal.SizeOf(typeof(Init))];
            temp[2] =(byte)PacketTypeServer.Init;
            byte[] temp2= BitConverter.GetBytes(id);
            Array.Copy(temp2, 0, temp, 3, 4);
           temp2 = BitConverter.GetBytes(protocol);
           Array.Copy(temp2, 0, temp, 7, 4);
           temp2 = BitConverter.GetBytes((short)(temp.Length - 2));
           Array.Copy(temp2, 0, temp, 0, 2);

            return temp;
        }

        /// <summary>
        /// выполняет основные действия
        /// </summary>
        /// <param name="e"></param>
        /// <returns></returns>
        private bool doAction(SocketAsyncEventArgs e)
        {
            AsyncUserToken token = (AsyncUserToken)e.UserToken;
            token.DoPack();
            PacketsBilder[] fromserver = token.GetFromServer();
            bool needrecive = true;
            for (int i = 0; i < fromserver.Length; i++)
            {
                byte[] t = fromserver[i].getPacket();

                //send Client Init pack
                Array.Copy(t, 0, e.Buffer, e.Offset, t.Length);
                e.SetBuffer(e.Offset, t.Length);
                
                StartSend(e);
                needrecive = false;
               
            }
            if (needrecive)
            {


                StartRecive(e);
            }
            return needrecive;
            
        }
        private void StartSend(SocketAsyncEventArgs e)
        {
           
            bool willRaiseEvent = ((AsyncUserToken)e.UserToken).Socket.SendAsync(e);
            if (!willRaiseEvent)
            {
                ProcessSend(e);
            }
        }
        private void StartRecive(SocketAsyncEventArgs e)
        {
            // done echoing data back to the client
            AsyncUserToken token = (AsyncUserToken)e.UserToken;
            // read the next block of data send from the client
              
            
                bool willRaiseEvent = token.Socket.ReceiveAsync(e);
                if (!willRaiseEvent)
                {
                    ProcessReceive(e);
                }
            
        }
    }
}
